<?php 
/**
 * Smtp - Simple Mail Transfer Protocol
 * 
 * 简单邮件传输协议发送到邮件服务器(邮件传输代理MTA)
 *
 * @author   songhuan <iphper@yeah.net>
 * @version   $Id: Smtp.php 1 2010-08-10 14:40:00Z $
 */
class Smtp
{
	/**
	 * @var string SMTP主机
	 */
	private $_host = null;
	
	/**
	 * @var int SMTP端口号，默认是 25
	 */
	private $_port = 25;
	
	/**
	 * @var string SMTP 用户名
	 */
	private $_username = null;
	
	/**
	 * @var string SMTP 密码
	 */
	private $_password = null;
	
	/**
	 * @var string 发件人地址，应该和用户名相同，如果不相同，有可能出错
	 */
	private $_from = null;
	
	/**
	 * @var boolean 是否验证用户名和密码
	 */
	private $_auth = true;
	
	/**
	 * @var resource fsockopen句柄
	 */
	private $_stream = null;
	
	/**
	 * @var boolean 同时发送多个邮件时，每个邮件中都会显示所有收件人，如果不想显示收件人地址，可以设为false
	 */
	public $showToAddress = true;
	
	/**
	 * @var boolean 是否显示发件人地址
	 */
	public $showFromAddress = true;
	
	private static $_instance = null;
	
	private function __construct()
	{	
		$this->_host = V::$config['email']['smtp_host'];
		$this->_username = V::$config['email']['smtp_username'];
		$this->_password = V::$config['email']['smtp_password'];
		$this->_from = V::$config['email']['from_email'];
	}
	
	private function __clone()
	{
	}
	
	public static function getInstance()
	{
		if (null === self::$_instance) 
			self::$_instance = new self();
		
		return self::$_instance;
	}
	
	/**
	 * 发送邮件
	 * 
	 * @param string $to 收件人地址
	 * @param string $subject 邮件主题
	 * @param string $body 邮件正文
	 * @return boolean 
	 */
	public function sendMail($to, $subject, $body)
	{	
		$this->_connect();
		if ($this->_auth) 
			$this->_auth();
		
		$this->_from();
		$tos = explode(',', $to);
		foreach ($tos as $_to) 
			$this->_to(trim($_to));
		
		$this->_sendData($to, $subject, $body);
		fwrite($this->_stream, "QUIT\r\n");
		return true;
	}
	
	/**
	 * 发送邮件正文数据
	 * 
	 * @param string $to 收件人地址
	 * @param string $subject 邮件主题
	 * @param string $body 邮件正文
	 * @return void 
	 */
	private function _sendData($to, $subject, $body)
	{
		fwrite($this->_stream, "DATA\r\n");
		$message = fgets($this->_stream, 512);
		if (substr($message, 0, 3) !== '354') 
			throw new Exception('DATA - ' . $message);
		
		$toAddress = $this->showToAddress ? $to : '';
		$fromAddress = $this->showFromAddress ? $this->_from : '';
		$body = "To: $toAddress\r\nFrom: $fromAddress\r\nSubject: $subject\r\n\r\n$body\r\n.\r\n";
		fwrite($this->_stream, $body);
	}
	
	/**
	 * 设置收件人地址
	 * 
	 * @param string $_to 收件人地址
	 * @return void
	 */
	private function _to($to)
	{
		if ($to) 
		{
			fwrite($this->_stream, "RCPT TO: <$to>\r\n");
			$message = fgets($this->_stream, 512);
			if (substr($message, 0, 3) !== '250') 
			{
				fwrite($this->_stream, "RCPT TO: <$to>\r\n");
				$message = fgets($this->_stream, 512);
				throw new Exception('RCPT TO - ' . $message);
			}
		}
	}
	
	/**
	 * 设置发件人地址
	 * @return void
	 */
	private function _from()
	{
		fwrite($this->_stream, 'MAIL FROM: <' . preg_replace('/.*\<(.+?)\>.*/', '\\1', $this->_from) . ">\r\n");
		$message = fgets($this->_stream, 512);
		if (substr($message, 0, 3) !== '250') 
		{
			fwrite($this->_stream, 'MAIL FROM: <' . preg_replace('/.*\<(.+?)\>.*/', '\\1', $this->_from) . ">\r\n");
			$message = fgets($this->_stream, 512);
			if (substr($message, 0, 3) !== '250') 
				throw new Exception('MAIL FROM - ' . $message);
		}
	}
	
	/**
	 * 验证登录名和密码
	 * @return void
	 */
	private function _auth()
	{
		fwrite($this->_stream, "AUTH LOGIN\r\n");
		$message = fgets($this->_stream, 512);
		if (substr($message, 0, 3) !== '334') 
			throw new Exception('AUTH LOGIN - ' . $message);
		
		fwrite($this->_stream, base64_encode($this->_username) . "\r\n");
		$message = fgets($this->_stream, 512);
		if (substr($message, 0, 3) !== '334') 
			throw new Exception('USERNAME - ' . $message);
		
		fwrite($this->_stream, base64_encode($this->_password) . "\r\n");
		$message = fgets($this->_stream, 512);
		if (substr($message, 0, 3) != 235) 
			throw new Exception('PASSWORD - ' . $message);
	}
	
	/**
	 * 链接邮件服务器
	 * @return void
	 */
	private function _connect()
	{
		$errno = 0;
		$errstr = '';
		if (!$this->_stream = @fsockopen($this->_host, $this->_port, $errno, $errstr, 30)) 
			throw new Exception('unable to connect to the SMTP server - errstr: ' . $errstr . ', errno: ' . $errno);
		
		stream_set_blocking($this->_stream, true);
		$message = fgets($this->_stream, 512);
		if (substr($message, 0, 3) !== '220') 
			throw new Exception('CONNECT - ' . $message);
		
		fwrite($this->_stream, ($this->_auth ? 'EHLO' : 'HELO') . " {$this->_username}\r\n");
		$message = fgets($this->_stream, 512);
		if (substr($message, 0, 3) !== '220' && substr($message, 0, 3) !== '250') 
			throw new Exception('HELO/EHLO - ' . $message);
		
		while (true) 
		{
			if(substr($message, 3, 1) !== '-' || empty($message)) 
				break;
			
			$message = fgets($this->_stream, 512);
		}
	}
}
